/*********************************************************************
 * Módulo 2. Curso de Experto en Desarrollo de Videojuegos
 * Autor: Carlos González Morcillo     Carlos.Gonzalez@uclm.es
 *
 * You can redistribute and/or modify this file under the terms of the
 * GNU General Public License ad published by the Free Software
 * Foundation, either version 3 of the License, or (at your option)
 * and later version. See <http://www.gnu.org/licenses/>.
 *
 * This file is distributed in the hope that it will be useful, but
 * WITHOUT ANY WARRANTY; without even the implied warranty of
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
 * General Public License for more details.
 *********************************************************************/
#include "MyFrameListener.h"

MyFrameListener::MyFrameListener(RenderWindow* win,
				 Camera* cam,
				 OverlayManager *om,
				 SceneManager *sm) {
  OIS::ParamList param;
  size_t windowHandle;  std::ostringstream wHandleStr;

  _camera = cam;  _overlayManager = om; _sceneManager = sm;

  win->getCustomAttribute("WINDOW", &windowHandle);
  wHandleStr << windowHandle;
  param.insert(std::make_pair("WINDOW", wHandleStr.str()));

  _inputManager = OIS::InputManager::createInputSystem(param);
  _keyboard = static_cast<OIS::Keyboard*>
    (_inputManager->createInputObject(OIS::OISKeyboard, false));
  _mouse = static_cast<OIS::Mouse*>
    (_inputManager->createInputObject(OIS::OISMouse, false));
  _mouse->getMouseState().width = win->getWidth();
  _mouse->getMouseState().height = win->getHeight();

  _broadphase = new btDbvtBroadphase();
  _collisionConf = new btDefaultCollisionConfiguration();
  _dispatcher = new btCollisionDispatcher(_collisionConf);
  _solver = new btSequentialImpulseConstraintSolver;
  _world = new btDiscreteDynamicsWorld(_dispatcher,_broadphase,
				       _solver,_collisionConf);

  // Establecimiento propiedades del mundo
  _world->setGravity(btVector3(0,-10,0));

  // Gestor de zombis
  _pZombieManager = NULL;

  _pHumanShape = NULL;
  _pHuman = NULL;

  _pStageShape = NULL;
  _pScaledStageShape = NULL;
  _pStageBody = NULL;

  _pDebugDrawer = NULL;

  // Creacion de los elementos iniciales del mundo
  CreateInitialWorld();
}

MyFrameListener::~MyFrameListener() {
  _inputManager->destroyInputObject(_keyboard);
  _inputManager->destroyInputObject(_mouse);
  OIS::InputManager::destroyInputSystem(_inputManager);

  _world->removeRigidBody(_groundRigidBody);
  delete _groundRigidBody->getMotionState();  delete _groundRigidBody;
  delete _groundShape;

  delete _pHuman;
  delete _pHumanShape;

  delete _pZombieManager;

  delete _pScaledStageShape;
  delete _pStageShape;
  delete _pStageBody;

  delete _world;  delete _solver;
  delete _collisionConf;
  delete _dispatcher;     delete _broadphase;
}

void MyFrameListener::CreateInitialWorld() {

  // Crea el escenario
  createStage();

  // Humano
  _pHumanShape = new btCylinderShape(btVector3(0.346 * 0.5 * 3,
                                               0.533 * 0.5 * 3,
                                               0.164 * 0.5 * 3));
  _pHuman = new Human(_world, _pHumanShape,
                      _sceneManager, OpenSteer::Vec3(0, 0, 3));


  // Horda de prueba
  _pZombieManager = new ZombieManager(_world, _sceneManager, _pHuman);
  _pZombieManager->createSpawner(OpenSteer::Vec3(0, 0, 0), 1, 0);
}

bool MyFrameListener::frameStarted(const Ogre::FrameEvent& evt) {
  Ogre::Vector3 vt(0,0,0);     Ogre::Real tSpeed = 20.0;
  Ogre::Real deltaT = evt.timeSinceLastFrame;
  int fps = 1.0 / deltaT;
  bool mbleft, mbmiddle, mbright; // Botones del raton pulsados

  _world->stepSimulation(deltaT, 1); // Actualizar simulacion Bullet

  _keyboard->capture();
  if (_keyboard->isKeyDown(OIS::KC_ESCAPE)) return false;

  // TODO Cambiar el movimiento del humano (atraviesa paredes)
  btVector3 impulse(0,0,0);
  if (_keyboard->isKeyDown(OIS::KC_I)) impulse += btVector3(0,0,-1);
  if (_keyboard->isKeyDown(OIS::KC_J)) impulse += btVector3(-1,0,0);
  if (_keyboard->isKeyDown(OIS::KC_K)) impulse += btVector3(0,0,1);
  if (_keyboard->isKeyDown(OIS::KC_L)) impulse += btVector3(1,0,0);
  if (impulse != btVector3(0, 0, 0)) {

    _pHuman->translate(impulse.normalize() * 10 * deltaT);
  }

  // Actualizar horda de zombis
  _pZombieManager->update(0, deltaT);

  // Actualizar humano
  _pHuman->update(0, deltaT);

  _pDebugDrawer->step();

  int posx = _mouse->getMouseState().X.abs;   // Posicion del puntero
  int posy = _mouse->getMouseState().Y.abs;   //  en pixeles.

  _camera->moveRelative(vt * deltaT * tSpeed);
  if (_camera->getPosition().length() < 10.0) {
    _camera->moveRelative(-vt * deltaT * tSpeed);
  }

  _mouse->capture();

 // Si usamos la rueda, desplazamos en Z la camara ------------------
  vt+= Ogre::Vector3(0,0,-10)*deltaT * _mouse->getMouseState().Z.rel;
  _camera->moveRelative(vt * deltaT * tSpeed);

  // Botones del raton pulsados? -------------------------------------
  mbleft = _mouse->getMouseState().buttonDown(OIS::MB_Left);
  mbmiddle = _mouse->getMouseState().buttonDown(OIS::MB_Middle);
  mbright = _mouse->getMouseState().buttonDown(OIS::MB_Right);

  if (mbmiddle) { // Con boton medio pulsado, rotamos camara ---------
    float rotx = _mouse->getMouseState().X.rel * deltaT * -1;
    float roty = _mouse->getMouseState().Y.rel * deltaT * -1;
    _camera->yaw(Ogre::Radian(rotx));
    _camera->pitch(Ogre::Radian(roty));
  }

  Ogre::OverlayElement *oe;
  oe = _overlayManager->getOverlayElement("cursor");
  oe->setLeft(posx);  oe->setTop(posy);

  oe = _overlayManager->getOverlayElement("fpsInfo");
  oe->setCaption(Ogre::StringConverter::toString(fps));

  return true;
}

bool MyFrameListener::frameEnded(const Ogre::FrameEvent& evt) {
  Real deltaT = evt.timeSinceLastFrame;
  _world->stepSimulation(deltaT, 1); // Actualizar simulacion Bullet
  return true;
}

void
MyFrameListener::createStage() {

  // Escenario
  Entity* stageEnt = _sceneManager->createEntity("escenario.mesh");
  SceneNode* stageNode = _sceneManager->getRootSceneNode()->createChildSceneNode();
  stageNode->attachObject(stageEnt);

  // Forma de colision
  Entity* collisionEnt = _sceneManager->createEntity("collision.mesh");
  BtOgre::StaticMeshToShapeConverter converter(collisionEnt);

  _pStageShape = converter.createTrimesh();

  _pScaledStageShape =
    new btScaledBvhTriangleMeshShape(_pStageShape, btVector3(3, 3, 3));

  MyMotionState* pMotionState = new MyMotionState
    (btTransform(btQuaternion(0, 0, 0, 1),
                 btVector3(0, 0, 0)),
     stageNode);

  btScalar mass = 0;
  btVector3 fallInertia(0,0,0);
  _pScaledStageShape->calculateLocalInertia(mass, fallInertia);

  btRigidBody:: btRigidBodyConstructionInfo
    bodyCI(mass, pMotionState, _pStageShape, fallInertia);

  // Cuerpo rígido
  _pStageBody = new btRigidBody(bodyCI);

  _world->addRigidBody(_pStageBody);

  _pDebugDrawer = new BtOgre::DebugDrawer(_sceneManager->getRootSceneNode(), _world);
  _world->setDebugDrawer(_pDebugDrawer);
}
